/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version status.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 *
 * Process bspd server status
 */

#include "bspd.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <evhttp.h>

#ifdef HAVE_MALLOC_H
#ifndef __OpenBSD__
#include <malloc.h>
#endif
#endif

extern struct conf setting;
char *status_str;
char tpl_filename[TPL_FILENAME_LENGTH];
char tpl_content[TPL_CONTENT_LENGTH];
char tpl_aped[TPL_CONTENT_LENGTH];
char tpl_tmp[TPL_CONTENT_LENGTH];
char tpl_loop[TPL_CONTENT_LENGTH];
int status_str_size;
int status_str_len = 0;

extern struct event_base *ev_base;

/* Initialize status server */
int status_init()
{
	if (!ev_base)
	{
		fprintf(stderr, "No event base initialized\n");
		log_add("No event base initialized");
		
		exit(EXIT_GENERAL);
	}
	
	if (BRIDGE_MODE_BASE == bridge_get_mode())
	{
		fprintf(stderr, "Status server cannot start under base mode\n");
		log_add("Status server cannot start under base mode");
		
		exit(EXIT_GENERAL);
	}
	
	struct evhttp *status_srv = evhttp_new(ev_base);

	if (!status_srv)
	{
		// Status server start error
		fprintf(stderr, "Status server startup failed\n");
		log_add("Status server startup failed");

		exit(EXIT_GENERAL);
	}

	char *srv_addr_str = setting.status_srv_listen_addr;
	int srv_port = setting.status_srv_port;
	unsigned long srv_addr = inet_addr(srv_addr_str);

	if (srv_addr < 0)
	{
		srv_addr = INADDR_ANY;
	}
	
	if (srv_port < 1024)
	{
		srv_port = DEFAULT_STATUS_SRV_PORT;
	}
	
	evhttp_bind_socket(status_srv, (const char *) srv_addr_str, (u_short) srv_port);
	evhttp_set_gencb(status_srv, status_request, NULL);

	queue_clock_cb_func_add(status_get_base_info_cb, NULL);

	fprintf(stderr, "Status server created on port %d\n", srv_port);
	log_add("Status server created on port %d", srv_port);

	return RTN_SUCCESS;
}

/* Get file content */
void status_file_content(const char *tplname)
{
	FILE *fp;
	size_t l = 0;

	sprintf(tpl_filename, "%s%s", setting.status_file_dir, tplname);

	fp = fopen(tpl_filename, "rb");
	if (!fp)
	{
		// Error 404
		return;
	}

	l = fread(tpl_content, 1, TPL_CONTENT_LENGTH, fp);
	fclose(fp);
	tpl_content[l] = 0x0;
	
	return;
}

/* Get binary file */
void status_binary_content(struct evhttp_request *r, const char *filename, struct evbuffer *buf)
{
	FILE *fp;
	size_t l = 0;

	sprintf(tpl_filename, "%s%s", setting.status_file_dir, filename);

	fp = fopen(tpl_filename, "rb");
	if (!fp)
	{
		// Error 404
		evbuffer_add_printf(buf, "<body><h1>Request file : %s not found</h1></body>", filename);
		
		return;
	}

	evhttp_add_header(r->output_headers, "Content-Type", status_mime_type(filename));

	while (!feof(fp))
	{
		l = fread(tpl_content, 1, TPL_CONTENT_LENGTH, fp);
		evbuffer_add(buf, tpl_content, l);
	}

	fclose(fp);
	
	return;
}

/* Generate index page */
void status_page_gen(struct evbuffer *buf)
{
	char t_var[256], *t_txt;
	time_t t_time;
	int i, j;
	
	// Load page
	status_file_content("INDEX.tpl");

	// assign overall
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_CENTER_VERSION__", BSP_VERSION);
	t_time = (time_t) center_init_time();
	sprintf(t_var, "%s", ctime(&t_time));
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_CENTER_START_TIME__", t_var);
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BRIDGE_MODE__", (bridge_get_mode() == BRIDGE_MODE_LOGIN) ? "Login" : ((bridge_get_mode() == BRIDGE_MODE_BASE || bridge_get_mode() == BRIDGE_MODE_CENTER) ? "Cluster" : "Single"));
	t_time = time(NULL);
	sprintf(t_var, "%s", ctime(&t_time));
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_CURRENT_TIME__", t_var);
	sprintf(t_var, "%d", (int) getpid());
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_CENTER_PID__", t_var);
	sprintf(t_var, "%lu", (unsigned long) (t_time - center_init_time()));
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_CENTER_RUNTIME__", t_var);

	// assign center
	sprintf(t_var, "%d", center_count_client());
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_ONLINE_CLIENT__", t_var);
	sprintf(t_var, "%d", center_count_regon());
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_REGON_TIME__", t_var);
	sprintf(t_var, "%d", center_count_regoff());
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_REGOFF_TIME__", t_var);
	sprintf(t_var, "%d", mempool_count_client());
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_TOTAL_USER__", t_var);
	sprintf(t_var, "%d", mempool_count_group());
	status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_TOTAL_GROUP__", t_var);
	sprintf(t_var, "%d", mempool_count_room());
	status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_TOTAL_ROOM__", t_var);

	// assign runner
	strcpy(tpl_tmp, tpl_content);
	tpl_loop[0] = 0x0;
	status_file_content("LOOP_RUNNER.tpl");
	t_txt = strdup(tpl_content);
	struct runner *r;

	for (i = 1; i < MAX_RUNNER; i ++)
	{
		r = runner_get(i);

		if (r)
		{
			sprintf(t_var, "%d", r->runner_id);
			status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_RUNNER_ID__", t_var);
			// ---
			status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_RUNNER_NAME__", r->runner_name);
			// ---
			status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_RUNNER_SCRIPT__", r->script_filename);
			sprintf(t_var, "%s", r->need_login ? "Yes" : "No");
			status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_RUNNER_NEED_LOGIN__", t_var);
			sprintf(t_var, "%d", r->load_time);
			status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_RUNNER_LOAD_TIME__", t_var);

			strncat(tpl_loop, tpl_aped, TPL_CONTENT_LENGTH - strlen(tpl_loop));
			strcpy(tpl_content, t_txt);
		}
	}

	free(t_txt);
	status_str_replace(tpl_tmp, tpl_content, "__BSP_STATUS_RUNNER_LIST__", tpl_loop);

	// assign base
	struct base_server_info *bi;
	strcpy(tpl_tmp, tpl_content);
	tpl_loop[0] = 0x0;
	status_file_content("LOOP_BASE.tpl");
	t_txt = strdup(tpl_content);
	j = (bridge_get_mode() == BRIDGE_MODE_UNITED) ?  0 : conn_client_list_size();

	for (i = 0; i <= j; i ++)
	{
		if (bridge_get_mode() == BRIDGE_MODE_CENTER && !conn_client_list_get(i))
		{
			continue;
		}

		bi = center_fetch_base_info(i);
		
		sprintf(t_var, "%d", bi->base_id);
		status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BASE_ID__", t_var);
		sprintf(t_var, "%lu", bi->base_addr);
		status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_BASE_ADDR__", t_var);
		sprintf(t_var, "%d", bi->base_port);
		status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BASE_PORT__", t_var);
		sprintf(t_var, "%s", bi->base_version);
		status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_BASE_VERSION__", t_var);
		t_time = (time_t) bi->start_time;
		sprintf(t_var, "%s", ctime(&t_time));
		status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BASE_START_TIME__", t_var);
		sprintf(t_var, "%d", bi->current_conn);
		status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_BASE_CURRENT_CLIENT__", t_var);
		sprintf(t_var, "%d", bi->max_conn);
		status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BASE_MAX_CLIENT__", t_var);
		sprintf(t_var, "%lu", bi->memory_usage);
		status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_BASE_PACKAGE_COMMAND__", t_var);
		sprintf(t_var, "%lu", bi->package_read);
		status_str_replace(tpl_content, tpl_aped, "__BSP_STATUS_BASE_PACKAGE_READ__", t_var);
		sprintf(t_var, "%lu", bi->package_send);
		status_str_replace(tpl_aped, tpl_content, "__BSP_STATUS_BASE_PACKAGE_SEND__", t_var);

		strncat(tpl_loop, tpl_content, TPL_CONTENT_LENGTH - strlen(tpl_loop));
		strcpy(tpl_content, t_txt);
	}
	
	free(t_txt);
	status_str_replace(tpl_tmp, tpl_content, "__BSP_STATUS_BASE_LIST__", tpl_loop);

	// Write to buffer
	evbuffer_add(buf, tpl_content, strlen(tpl_content));
	
	return;
}

/* Handle a HTTP request */
void status_request(struct evhttp_request *r, void *arg)
{
	struct evbuffer *buf = evbuffer_new();
	const char *request_uri = evhttp_request_uri(r);

	if (0 == strcmp(request_uri, "/") || 0 == strncmp(request_uri, "/status", 7))
	{
		// Index page
		status_page_gen(buf);
	}

	else if (0 == strncmp(request_uri, "/reload_runner_", 15))
	{
		// Reload runner
		int runner_id = atoi(strrchr(request_uri, '_') + 1);
		runner_reload(runner_id);

		evbuffer_add_printf(buf, "<script>window.location.href=\"/\";</script>");
	}

	else
	{
		// Fetch file
		status_binary_content(r, request_uri, buf);
	}

	evhttp_send_reply(r, HTTP_OK, "OK", buf);
	evbuffer_free(buf);
	
	return;
}

/* Get information of base server */
void status_get_base_info_cb(void *arg)
{
	static char sgn[8];
	int i, j;
	static int timer = 0;

	timer = (timer >= setting.base_status_interval) ? 0 : timer;
	
	if (++ timer != 2)
	{
		return;
	}

	put_u_int(SGN_CMD_STATUS, sgn);
	put_u_int(0, sgn + 4);
	
	j = (bridge_get_mode() == BRIDGE_MODE_UNITED) ?  0 : conn_client_list_size();
	
	for (i = 0; i <= j; i ++)
	{
		if (bridge_get_mode() == BRIDGE_MODE_CENTER && !conn_client_list_get(i))
		{
			continue;
		}

		bridge_to_base(i, sgn, 8);
	}
	
	return;
}

/* String replacement */
void status_str_replace(const char *input, char *output, char *search, char *niddle)
{
	char *pi, *po, *p;
	int leni, leno, len;

	pi = (char *) input;
	po = output;

	leni = strlen(search);
	leno = strlen(niddle);

	p = strstr(input, search);

	if (p)
	{
		while (p)
		{
			len = (int) (p - pi);
			memcpy(po, pi, len);
			memcpy(po + len, niddle, leno);

			pi = p + leni;
			po = po + len + leno;
			
			p = strstr(pi, search);
		}

		strcpy(po, pi);
	}

	else
	{
		strcpy(po, pi);
	}
}

/* Get mime type */
const char * status_mime_type(const char *filename)
{
	char *dot;
	int i;

	dot = strrchr(filename, '.');

	const struct
	{
		const char *suffix;
		const char *mime_type;
	} types[] = 
	{
		{"txt", "text/plain"}, 
		{"c", "text/plain"}, 
		{"cpp", "text/plain"}, 
		{"h", "text/plain"}, 
		{"hpp", "text/plain"},
		{"as", "text/plain"}, 
		
		{"css", "text/css"}, 
		{"js", "text/javascript"},
		{"xml", "text/xml"}, 
		{"htm", "text/html"}, 
		{"html", "text/html"}, 
		{"shtml", "text/html"}, 
		{"hts", "text/html"}, 

		{"gif", "image/gif"}, 
		{"png", "image/png"}, 
		{"bmp", "application/x-ms-bmp"}, 
		{"jpg", "image/jpeg"}, 
		{"jpeg", "image/jpeg"}, 
		{"jpe", "image/jpeg"}, 

		{"wav", "audio/wav"}, 
		{"midi", "audio/midi"}, 
		{"mid", "audio/midi"}, 
		{"mp3", "audio/mpeg"}, 
		{"mp2", "audio/mpeg"}, 
		{"avi", "video/x-msvideo"}, 
		{"mov", "video/quicktime"}, 

		{"pdf", "application/pdf"}, 
		{"rtf", "application/rtf"}, 
		{"doc", "application/msword"}, 
		{"xls", "application/msexcel"}, 
		{"ppt", "application/mspowerpoint"}, 
		{"swf", "application/x-shockwave-flash"}, 

		{"zip", "application/zip"}, 
		{"rar", "application/x-rar-compressed"}, 
		{"gz", "application/x-gzip"}, 
		{"jar", "application/java-archive"}, 
		{"tgz", "application/x-tar"}, 
		{"tar", "application/x-tar"}
	};

	for (i = 0; dot && i < 38; i ++)
	{
		if (0 == strcasecmp(dot + 1, types[i].suffix))
		{
			return types[i].mime_type;
		}
	}
	
	return "application/octet-stream";
}
